<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>nsp_test client</title>
    <style>
      :root {
        --bg: #f4f6f8;
        --card: #ffffff;
        --text: #1f2328;
        --muted: #6b7280;
        --primary: #4f8ef7;
        --primary-weak: #e8f0fe;
        --danger: #ef4444;
      }
      * { box-sizing: border-box; }
      body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, Noto Sans, "PingFang SC", "Microsoft Yahei", sans-serif; background: var(--bg); color: var(--text); }
      .container { max-width: 760px; margin: 0 auto; padding: 16px; }
      .chat { background: var(--card); border-radius: 12px; box-shadow: 0 6px 18px rgba(0,0,0,.06); display: flex; flex-direction: column; height: calc(100vh - 32px); }
      .header { padding: 12px 16px; border-bottom: 1px solid #eef0f2; display: flex; align-items: center; justify-content: space-between; }
      .title { font-weight: 600; }
      .status { display: inline-flex; align-items: center; gap: 8px; color: var(--muted); font-size: 13px; }
      .dot { width: 8px; height: 8px; border-radius: 50%; background: #aaa; display: inline-block; }
      .dot.online { background: #22c55e; }
      .toolbar { display: flex; gap: 8px; padding: 10px 16px; border-bottom: 1px solid #eef0f2; align-items: center; }
      .toolbar input { flex: 1; min-width: 0; padding: 8px 10px; border: 1px solid #e5e7eb; border-radius: 8px; }
      .toolbar button { padding: 8px 12px; border: none; background: var(--primary); color: #fff; border-radius: 8px; cursor: pointer; }
      .toolbar button:disabled { background: #9bbcf9; cursor: not-allowed; }
      .messages { flex: 1; overflow: auto; padding: 16px; display: flex; flex-direction: column; gap: 8px; background: linear-gradient(180deg, #fafbfc, #f6f8fb); }
      .msg { display: flex; gap: 8px; align-items: flex-end; }
      .msg.other { justify-content: flex-start; }
      .msg.self { justify-content: flex-end; }
      .avatar { width: 28px; height: 28px; border-radius: 50%; background: #d1d5db; display: none; }
      .msg.other .avatar { display: block; }
  .bubble { max-width: 100%; padding: 8px 10px; border-radius: 12px; line-height: 1.4; white-space: pre-wrap; word-break: break-word; overflow-x: hidden; overflow-y: visible; }
      .bubble.other { background: #f1f5f9; color: var(--text); border-top-left-radius: 4px; }
      .bubble.self { background: var(--primary); color: #fff; border-top-right-radius: 4px; }
      .meta { font-size: 12px; color: var(--muted); margin: 0 4px 2px; }
      .meta.self { text-align: right; }
      .system { text-align: center; color: var(--muted); font-size: 12px; margin: 8px 0; }
      .typing { padding: 4px 16px; color: var(--muted); font-size: 12px; min-height: 20px; }
      .composer { display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid #eef0f2; }
      .composer input { flex: 1; min-width: 0; padding: 10px 12px; border: 1px solid #e5e7eb; border-radius: 10px; }
      .composer button { padding: 10px 14px; border: none; background: var(--primary); color: #fff; border-radius: 10px; cursor: pointer; }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="chat">
        <div class="header">
          <div class="title">Namespace: /nsp_test</div>
          <div class="status">
            <span id="statusDot" class="dot"></span>
            <span id="statusText">Disconnected</span>
          </div>
        </div>

        <div class="toolbar">
          <input id="username" value="webuser" placeholder="输入用户名" />
          <button id="loginBtn">登录</button>
        </div>

        <div id="messages" class="messages"></div>
        <div id="typing" class="typing"></div>

        <div class="composer">
          <input id="msg" placeholder="输入消息，按 Enter 发送" />
          <button id="sendBtn">发送</button>
        </div>
      </div>
    </div>

    <script src="/socket.io/socket.io.js"></script>
    <script>
      const messagesEl = document.getElementById('messages');
      const typingEl = document.getElementById('typing');
      const statusDot = document.getElementById('statusDot');
      const statusText = document.getElementById('statusText');
      const usernameInput = document.getElementById('username');
      const loginBtn = document.getElementById('loginBtn');
      const msgInput = document.getElementById('msg');
      const sendBtn = document.getElementById('sendBtn');

      let currentUsername = '';
      let typingTimeout;

      function addSystem(text) {
        const p = document.createElement('div');
        p.className = 'system';
        p.textContent = text;
        messagesEl.appendChild(p);
        messagesEl.scrollTop = messagesEl.scrollHeight;
      }

      function addMessage({ username, text, self }) {
        const wrap = document.createElement('div');
        wrap.className = 'msg ' + (self ? 'self' : 'other');

        const avatar = document.createElement('div');
        avatar.className = 'avatar';
        const box = document.createElement('div');

        const meta = document.createElement('div');
        meta.className = 'meta ' + (self ? 'self' : 'other');
        meta.textContent = username || 'Unknown';

        const bubble = document.createElement('div');
        bubble.className = 'bubble ' + (self ? 'self' : 'other');
        bubble.textContent = text;

        box.appendChild(meta);
        box.appendChild(bubble);

        if (!self) wrap.appendChild(avatar);
        wrap.appendChild(box);

        messagesEl.appendChild(wrap);
        messagesEl.scrollTop = messagesEl.scrollHeight;
      }

      // 连接 /nsp_test 命名空间
      const socket = io('/nsp_test');

      socket.on('connect', () => {
        statusDot.classList.add('online');
        statusText.textContent = 'Connected';
        addSystem('已连接 /nsp_test');
      });

      socket.on('disconnect', (reason) => {
        statusDot.classList.remove('online');
        statusText.textContent = 'Disconnected';
        addSystem('已断开：' + reason);
      });

      socket.on('login', (data) => {
        addSystem(`登录成功，当前在线：${(data && data.numUsers) || '-'} 人`);
      });

      socket.on('user joined', (data) => {
        addSystem(`${data.username || '某人'} 加入了聊天（${data.numUsers} 人在线）`);
      });

      socket.on('user left', (data) => {
        addSystem(`${data.username || '某人'} 离开了聊天（${data.numUsers} 人在线）`);
      });

      socket.on('new message', (data) => {
        // 服务端广播的是“别人”的消息
        if (!data) return;
        addMessage({ username: data.username, text: data.message, self: false });
      });

      socket.on('typing', (data) => {
        typingEl.textContent = `${data && data.username ? data.username : '某人'} 正在输入…`;
        clearTimeout(typingTimeout);
        typingTimeout = setTimeout(() => { typingEl.textContent = ''; }, 1200);
      });

      socket.on('stop typing', () => {
        typingEl.textContent = '';
      });

      function doLogin() {
        const name = (usernameInput.value || '').trim();
        if (!name) { addSystem('请输入用户名再登录'); return; }
        currentUsername = name;
        socket.emit('add user', name);
        addSystem(`你已登录为：${name}`);
      }

      function sendMessage() {
        const txt = msgInput.value.trim();
        if (!txt) return;
        if (!currentUsername) { addSystem('请先登录再发送消息'); return; }
        // 本地自回显（服务器使用 broadcast，不会回发给自己）
        addMessage({ username: currentUsername, text: txt, self: true });
        socket.emit('new message', txt);
        msgInput.value = '';
        socket.emit('stop typing');
      }

      loginBtn.onclick = doLogin;
      sendBtn.onclick = sendMessage;
      msgInput.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
          e.preventDefault();
          sendMessage();
        } else {
          if (currentUsername) {
            socket.emit('typing');
            clearTimeout(typingTimeout);
            typingTimeout = setTimeout(() => socket.emit('stop typing'), 800);
          }
        }
      });
    </script>
  </body>
</html>
